﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace Sencha
{
	/// <summary>
	/// Cache dictionary implementation with ReaderWriterLock implemented in
	/// </summary>
	class ReaderWriterLockCache<KeyT, ValueT>
	{
		private Dictionary<KeyT, ValueT> innerCache;
		private ReaderWriterLockSlim cacheLock;

		/// <summary>
		/// Default contructor with internal dictionary capacity parameter
		/// </summary>
		public ReaderWriterLockCache(int capacity)
		{
			innerCache = new Dictionary<KeyT, ValueT>(capacity);
			cacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
		}

		public ValueT FetchValueOrDefault(KeyT key)
		{
			cacheLock.EnterReadLock();
			try
			{
				var value = default(ValueT);
				innerCache.TryGetValue(key, out value);
				return value;
			}
			finally
			{
				cacheLock.ExitReadLock();
			}
		}
		public void SetValue(KeyT key, ValueT value)
		{
			cacheLock.EnterWriteLock();
			try
			{
				innerCache[key] = value;
			}
			finally
			{
				cacheLock.ExitWriteLock();
			}
		}
		public void AddValue(KeyT key, ValueT value)
		{
			cacheLock.EnterWriteLock();
			try
			{
				innerCache.Add(key, value);
			}
			finally
			{
				cacheLock.ExitWriteLock();
			}
		}
		public ValueT ExchangeValue(KeyT key, ValueT value)
		{
			cacheLock.EnterWriteLock();
			try
			{
				var oldValue = default(ValueT);
				innerCache.TryGetValue(key, out oldValue);
				innerCache[key] = value;
				return oldValue;
			}
			finally
			{
				cacheLock.ExitWriteLock();
			}
		}
		public ValueT FetchOrCreateValue(KeyT key, Func<ValueT> creator)
		{
			if (creator == null)
				throw new ArgumentNullException("creator");

			cacheLock.EnterReadLock();
			try
			{
				var value = default(ValueT);
				if (innerCache.TryGetValue(key, out value))
					return value;
			}
			finally
			{
				cacheLock.ExitReadLock();
			}
			cacheLock.EnterWriteLock();
			try
			{
				var value = default(ValueT);
				if (innerCache.TryGetValue(key, out value))
					return value;

				value = creator();
				innerCache.Add(key, value);

				return value;
			}
			finally
			{
				cacheLock.ExitWriteLock();
			}
		}
	}
}
